﻿using System.Runtime.Serialization;

namespace Swimj.Core.Utilities
{
    public interface ITypeBasedId<out TIdType>
    {
        TIdType? Id { get; }
    }

    public interface ITypeBasedId : ITypeBasedId<object>
    { }

    [DataContract]
    public abstract class BaseId<T, TIdType> : IEquatable<T>, ITypeBasedId<TIdType>
        where T : BaseId<T, TIdType>
    {
        protected BaseId()
        { }

        protected BaseId(TIdType id)
        {
            if (typeof(TIdType) == typeof(Guid))
            {
                Guid localId = (Guid)(object)id!;
                if (localId.Equals(Guid.Empty))
                    throw new ArgumentException("Argument of type GUID must not be EMPTY");
            }
            if (typeof(TIdType) == typeof(int))
            {
                int localId = (int)(object)id!;
                if (localId < 0)
                    throw new ArgumentException("Argument of type INT must be >= 0");
            }

            Id = id;
        }

        [DataMember]
        public TIdType? Id { get; private set; }

        public override string? ToString()
        {
            return Id?.ToString();
        }

        public override bool Equals(object? obj)
        {
            return Equals(obj as T);
        }

        public bool Equals(T? other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id?.Equals(other.Id) ?? false;
        }

        public override int GetHashCode()
        {
            return Id?.GetHashCode() ?? 0;
        }

        public static bool operator ==(BaseId<T, TIdType> id1, BaseId<T, TIdType> id2)
        {
            if ((object)id1 == null)
            {
                if ((object)id2 == null)
                    return true;
                return false;
            }
            return id1.Equals(id2);
        }

        public static bool operator !=(BaseId<T, TIdType> id1, BaseId<T, TIdType> id2)
        {
            return !(id1 == id2);
        }

        public static implicit operator TIdType(BaseId<T, TIdType> id)
        {
            return id.Id!;
        }
    }
}
